package com.plan.service.card.impl;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.plan.base.config.property.AppProperty;
import com.plan.base.constant.BaseCode;
import com.plan.base.constant.Constant;
import com.plan.base.entity.Result;
import com.plan.base.util.DateUtil;
import com.plan.controller.card.request.BaseReq;
import com.plan.controller.card.request.CopyRepositoryTaskReq;
import com.plan.controller.card.response.TaskListVO;
import com.plan.controller.user.response.UserVO;
import com.plan.dao.mapper.card.*;
import com.plan.dao.service.card.MyTaskRepositoryDaoService;
import com.plan.dao.service.card.TaskRepositoryDaoService;
import com.plan.entity.card.*;
import com.plan.enums.AgeLevelEnum;
import com.plan.enums.DayPlanStatusEnum;
import com.plan.enums.DelFlagEnum;
import com.plan.service.card.TaskRepositoryService;
import com.plan.service.card.TeamService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class TaskRepositoryServiceImpl implements TaskRepositoryService {
    @Resource
    private TeamService teamService;
    @Resource
    private CardMapper cardMapper;
    @Resource
    private TeamMapper teamMapper;
    @Resource
    private DayPlanMapper dayPlanMapper;
    @Resource
    private DayPlanStatMapper dayPlanStatMapper;
    @Resource
    private TaskRepositoryMapper taskRepositoryMapper;
    @Resource
    private MyTaskRepositoryMapper myTaskRepositoryMapper;
    @Resource
    private TaskRepositoryDaoService taskRepositoryDaoService;
    @Resource
    private MyTaskRepositoryDaoService myTaskRepositoryDaoService;
    @Resource
    private AppProperty appProperty;
    /**
     * 缓存系统任务
     */
    private LoadingCache<BaseReq, List<TaskListVO>> sysTaskListCache;
    /**
     * 缓存积分卡的任务列表
     */
    private LoadingCache<BaseReq, List<TaskListVO>> myTaskListCache;

    @PostConstruct
    public void init() {
        sysTaskListCache = CacheBuilder.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(new CacheLoader<BaseReq, List<TaskListVO>>() {
                    @Override
                    public List<TaskListVO> load(BaseReq baseReq) {
                        return getSysListFromDB(baseReq);
                    }
                });
        myTaskListCache = CacheBuilder.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(new CacheLoader<BaseReq, List<TaskListVO>>() {
                    @Override
                    public List<TaskListVO> load(BaseReq req) {
                        return getMyListFromDB(req);
                    }
                });
    }

    public List<TaskListVO> getSysListFromDB(BaseReq req) {
        Card card = cardMapper.selectOneById(req.getTeamId(), req.getCardId());
        if (card == null) {
            return Collections.emptyList();
        }
        int level = AgeLevelEnum.getLevelForBirthday(card.getBirthday());
        List<TaskListVO> list = taskRepositoryMapper.getList(level, Constant.SYS_TEAM_ID);
        if (list == null) {
            return Collections.emptyList();
        }
        return list;
    }

    public List<TaskListVO> getMyListFromDB(BaseReq req) {
        Card card = cardMapper.selectOneById(req.getTeamId(), req.getCardId());
        if (card == null) {
            return Collections.emptyList();
        }
        List<TaskListVO> list = myTaskRepositoryMapper.getList(req.getCardId(), card.getTeamId());
        if (!CollectionUtils.isEmpty(list)) {
            list.sort(Comparator.comparingInt(TaskListVO::getSort));
        }
        return list;
    }

    @Override
    public Result<List<TaskListVO>> sysListFromCache(BaseReq req) {
        try {
            List<TaskListVO> list = sysTaskListCache.get(req);
            return Result.ok(list);
        } catch (ExecutionException e) {
            log.error("sysListFromCache error teamId={}，cardId={}", req.getTeamId(), req.getCardId(), e);
            return Result.fail(BaseCode.SYS_ERROR);
        }
    }

    @Override
    public Result<List<TaskListVO>> myList(BaseReq req) {
        try {
            List<TaskListVO> list = myTaskListCache.get(req);
            return Result.ok(list);
        } catch (ExecutionException e) {
            log.error("myTaskListFromCache error level={}", req.getTeamId(), e);
            return Result.fail(BaseCode.SYS_ERROR);
        }
    }


    @Override
    public Result<Long> insert(MyTask task) {
        Integer maxSort = myTaskRepositoryMapper.getMaxSort(task.getCardId());
        if (maxSort == null) {
            maxSort = 0;
        }
        task.setSort(maxSort + 1);
        Team team = teamMapper.selectOneById(task.getTeamId());
        if (team.getTaskRepositoryMaxNum().compareTo(appProperty.getTaskRepositoryMaxNum()) > 0) {
            return Result.fail(BaseCode.TASK_REPOSITORY_LIMIT_ERROR);
        }
        team.setTaskRepositoryMaxNum(team.getTaskRepositoryMaxNum() + 1);

        myTaskRepositoryDaoService.insert(task, team);

        this.clearMyTaskCache(task.getTeamId(), task.getCardId());
        return Result.ok(task.getId());
    }

    @Override
    public Result<Long> delete(Long id, UserVO user) {
        Task task = taskRepositoryMapper.getOneById(id);
        if (task == null) {
            return Result.ok();
        }
        if (!teamService.isUserInTeam(task.getTeamId(), user.getId())) {
            return Result.fail(BaseCode.ILLEGAL_OPERATION);
        }
        Team team = teamMapper.selectOneById(task.getTeamId());
        team.setTaskRepositoryMaxNum(team.getTaskRepositoryMaxNum() - 1);
        taskRepositoryDaoService.delete(task.getId(), team);
        return Result.ok();
    }

    @Override
    public Result<Long> copyRepositoryTask(CopyRepositoryTaskReq req, UserVO user) {
        List<Task> list = taskRepositoryMapper.getListByIds(req.getTaskIdList());
        List<MyTask> myTaskList = myTaskRepositoryMapper.getListByIds(req.getTaskIdList());
        if (CollectionUtils.isEmpty(list) && CollectionUtils.isEmpty(myTaskList)) {
            return Result.fail(BaseCode.PARAM_ERROR);
        }
        int yearMonthDay = DateUtil.getYearMonthDayFromDate(new Date());

        int existCount = dayPlanMapper.getCount(req.getTeamId(), req.getCardId(), yearMonthDay);
        if (req.getTaskIdList().size() + existCount > appProperty.getDayPlanMax()) {
            return Result.fail(BaseCode.DAY_PLAN_LIMIT_ERROR);
        }
        int totalPoint = 0;
        List<DayPlan> dayPlanList = new ArrayList<>(list.size());
        for (Task task : list) {
            DayPlan dayPlan = new DayPlan();
            dayPlan.setTeamId(req.getTeamId());
            dayPlan.setCardId(req.getCardId());
            dayPlan.setYearMonthDay(yearMonthDay);
            dayPlan.setUpdaterId(user.getId());
            dayPlan.setCreatorId(user.getId());
            dayPlan.setCardId(req.getCardId());
            dayPlan.setName(task.getName());
            dayPlan.setUpdaterId(user.getId());
            dayPlan.setCreatorId(user.getId());
            dayPlan.setAwardPoint(task.getPoint());
            dayPlan.setStatus(DayPlanStatusEnum.NEW.getStatus());
            dayPlan.setDelFlag(DelFlagEnum.NORMAL.getType());
            dayPlanList.add(dayPlan);
            totalPoint += task.getPoint();
        }
        for (MyTask task : myTaskList) {
            DayPlan dayPlan = new DayPlan();
            dayPlan.setTeamId(req.getTeamId());
            dayPlan.setCardId(req.getCardId());
            dayPlan.setYearMonthDay(yearMonthDay);
            dayPlan.setUpdaterId(user.getId());
            dayPlan.setCreatorId(user.getId());
            dayPlan.setCardId(req.getCardId());
            dayPlan.setName(task.getName());
            dayPlan.setUpdaterId(user.getId());
            dayPlan.setCreatorId(user.getId());
            dayPlan.setAwardPoint(task.getPoint());
            dayPlan.setStatus(DayPlanStatusEnum.NEW.getStatus());
            dayPlan.setDelFlag(DelFlagEnum.NORMAL.getType());
            dayPlanList.add(dayPlan);
            totalPoint += task.getPoint();
        }
        DayPlanStat dayPlanStat = dayPlanStatMapper.getOneByYearMonthDay(req.getTeamId(), req.getCardId(), yearMonthDay);
        if (dayPlanStat == null) {
            dayPlanStat = new DayPlanStat();
            dayPlanStat.setCardId(req.getCardId());
            dayPlanStat.setTeamId(req.getTeamId());
            dayPlanStat.setTaskNum(list.size());
            dayPlanStat.setYearMontDay(yearMonthDay);
            dayPlanStat.setGetPoint(totalPoint);
        } else {
            dayPlanStat.setGetPoint(totalPoint + dayPlanStat.getGetPoint());
            dayPlanStat.setTaskNum(dayPlanStat.getTaskNum() + list.size());
        }
        taskRepositoryDaoService.copyRepositoryTask(dayPlanStat, dayPlanList);
        return Result.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Long> addAfter(MyTask task, UserVO user) {
        MyTask old = myTaskRepositoryMapper.getOneById(task.getId());
        if (old == null) {
            return Result.fail(BaseCode.PARAM_ERROR);
        }
        // 一定要在插入之前查回来，避免把新插的数据查回来了
        List<MyTask> afterList = myTaskRepositoryMapper.getAfterSortTask(old.getCardId(), old.getSort());

        task.setId(null);
        task.setCardId(old.getCardId());
        task.setCreatorId(user.getId());
        task.setUpdaterId(user.getId());
        task.setDelFlag(DelFlagEnum.NORMAL.getType());
        task.setSort(old.getSort() + 1);

        Team team = teamMapper.selectOneById(task.getTeamId());
        if (team.getTaskRepositoryMaxNum().compareTo(appProperty.getTaskRepositoryMaxNum()) > 0) {
            return Result.fail(BaseCode.TASK_REPOSITORY_LIMIT_ERROR);
        }
        team.setTaskRepositoryMaxNum(team.getTaskRepositoryMaxNum() + 1);
        myTaskRepositoryDaoService.insert(task, team);

        if (CollectionUtils.isEmpty(afterList)) {
            this.clearMyTaskCache(team.getId(), task.getCardId());
            return Result.ok(task.getId());
        }

        // 插入一条数据之后，后面的排序都要更新
        for (MyTask t : afterList) {
            myTaskRepositoryMapper.updateSort(t.getId(), t.getSort() + 1);
        }
        this.clearMyTaskCache(team.getId(), task.getCardId());
        return Result.ok(task.getId());
    }

    @Override
    public void clearMyTaskCache(Long teamId, Long cardId) {
        BaseReq req = new BaseReq();
        req.setTeamId(teamId);
        req.setCardId(cardId);
        myTaskListCache.invalidate(req);
    }

}
